package KademliaDht

import (
	"os"
	"fmt"
)

type NodeLookup struct {
	Node		*KadNode
	Search		uint64
	Visited		map[uint64] *KadContact
	Shortlist	map[uint64] *KadContact
	Received	map[uint64] *KadContact
	ClosestNode	*KadContact
	ClosestDist uint64
}

func New_NodeLookup( node *KadNode, search uint64, shortlist []*KadContact ) *NodeLookup {
	sh := map[uint64] *KadContact { }
	for _, c := range shortlist {
		sh[c.Id] = c
	}
	return &NodeLookup{ node, search, map[uint64] *KadContact { }, sh, map[uint64] *KadContact { }, nil, 0 }
}

func ( nl *NodeLookup ) Find_Node() {
	finish := false
	/*fmt.Printf("SHORTLIST\n")
				for _, c := range nl.Shortlist {
					fmt.Printf("\t%d\n", c.Id)
				}*/
	if len( nl.Shortlist ) == 0 {
		finish = true
	}
	for !finish {
		alpha := nl.GetNextAlpha()
		if( len( alpha ) > 0 ) {
			// TODO: Make Paralel
			forward := false
			for _, c := range alpha {
				ks, error := c.Find_Node( nl.Node.MyContact, nl.Search )
				nl.Visited[c.Id] = c
				if error != nil {
					// TODO: Treat Error
					fmt.Print( error )
				} else {
					forward = forward || nl.ReceivedK( ks )
				}
				/*fmt.Printf("SHORTLIST\n")
				for _, c := range nl.Shortlist {
					fmt.Printf("\t%d\n", c.Id)
				}*/
			}
			if !forward && nl.shortlistVisited() {
				finish = true
			}
		} else {
			finish = true
		}
	}
}

func ( nl *NodeLookup ) Find_Value() ( value []byte, error os.Error ) {
	finish := false
	if len( nl.Shortlist ) == 0 {
		finish = true
	}
	for !finish {
		alpha := nl.GetNextAlpha()
		if( len( alpha ) > 0 ) {
			// TODO: Make Paralel
			forward := false
			for _, c := range alpha {
				v, ks, error := c.Find_Value( nl.Node.MyContact, nl.Search )
				nl.Visited[c.Id] = c
				if error != nil {
					return nil, error
				} else if v != nil {
					return v, nil
				} else {
					forward = forward || nl.ReceivedK( ks )
				}
			}
			if !forward && nl.shortlistVisited() {
				finish = true
			}
		} else {
			finish = true
		}
	}
	return nil, os.NewError( "Value not found." )
}

func ( nl *NodeLookup ) GetNextAlpha() []*KadContact {
	alpha := make( []*KadContact, 0, 0 )
	for k, c := range nl.Shortlist {
		_, v := nl.Visited[k]
		if k != nl.Node.MyContact.Id && !v && len( alpha ) < nl.Node.Alpha {
			alpha = append( alpha, c )
		}
	}
	return alpha
}

func ( nl *NodeLookup ) shortlistVisited() bool {
	v := true
	for k, _ := range nl.Shortlist {
		_, vs := nl.Visited[k]
		v = v && vs
	}
	return v
}

func ( nl *NodeLookup ) ReceivedK( cs []*KadContact ) bool {
	/*fmt.Printf("ReceivedK:\n")
	for _, c := range cs {
		fmt.Printf("\t%d\n", c.Id)
	}*/
	closer := false
	for _, c := range cs {
		result := nl.received( c )
		if( !closer ) {
			closer = result
		}
	}
	return closer
}

func ( nl *NodeLookup ) received( c *KadContact ) bool {
	if c.Id != nl.Node.MyContact.Id {
		nl.Node.Buckets.Spotted( c, Distance( nl.Node.MyContact.Id, c.Id ) )
	}

	_, exists := nl.Received[c.Id]
	if( exists ) {
		return false;
	}
	
	nl.Received[c.Id] = c

	/*_, exists := nl.Visited[c.Id]
	if( exists ) {
		return false;
	}
	
	_, exists = nl.Shortlist[c.Id]
	if( exists ) {
		return false;
	}*/
	
	dist := Distance( nl.Search, c.Id )
	if( nl.ClosestNode == nil || dist < nl.ClosestDist ) {
		nl.ClosestNode = c
		nl.ClosestDist = dist
	}
	
	max, max_distance := nl.max()
	//fmt.Printf( "N:%d\tD:%d LEN:%d MAX:%d MAX_D:%d\n", c.Id, dist, len( nl.Shortlist ), max, max_distance );
	if len( nl.Shortlist ) < nl.Node.K {
		nl.Shortlist[c.Id] = c, true
	} else if( dist < max_distance ) {
		//fmt.Printf( "LEN:%d\n", len( nl.Shortlist ) );
		nl.Shortlist[max] = nil, false
		//fmt.Printf( "LEN:%d\n", len( nl.Shortlist ) );
		nl.Shortlist[c.Id] = c, true
		return true
	}	
	
	return false
	
}

// Auxiliar methods

// TODO: Find cleaner solution
func ( nl *NodeLookup ) max() ( max uint64, max_distance uint64 ) {
	max = 0
	max_distance = 0
	for k, _ := range nl.Shortlist {
		d := Distance( nl.Search, k )
		if max_distance == 0 || d  > max_distance {
			max = k
			max_distance = d
		}
	}
	if max == 0 || max_distance == 0 {
		fmt.Printf( "MAX: %d DIST: %d\n", max, max_distance )
	}
	return max, max_distance
}




